A First-Principles-Driven Life

Posts tagged "system programming":

20 May 2008

用ldscript实现编译期功能插入

动机

设想,如果你想在程序中支持多种后端(backends),但在开发过程中并不能预计会有多少种。一般来说,会想着抽象出这种后端的描述, 然后用动态模块的方式,如 shared library 实现所谓的插件。使用插件,可以实现动态的功能插入,但如果你不想动态的功能插入,所 有功能在编译期间就已经确定呢。当然你可以使用一个数组硬编码维护,但这样增删功能就需要改代码,这里介绍一种使用 ld script 的方法。这种方法在 linux kernel 的代码中也使用到了。

关于 ld script

即使你不知道, ld script 在每次你链接程序的时候都被使用了,ld程序自带一个默认的脚本,如果你不指定其他的脚本就使用默认的。 ld script 的作用是描述如何组织从目标文件中得到的所有段,最终链接成最终的 elf 输出。关于 ld script 更多的信息,可以参考 ld 的 info page。

定义 ldscript

这里实现一个简单的示例程序,结构如下:主程序遍历一个外部静态数组,打印里面的所有字符串,直到结束;其他目标文件分别向该数 组插入一个或多个字符串,而无须修改主程序的代码或者执行额外的启动代码。

#+name main.c

#include <stdio.h>
#include <string.h>

extern const char array_begin;
extern const char array_end;

int main ( void ){
  const char *array_iter;
  printf( "array_begin : %p , array_end : %p\n", &array_begin, &array_end);
  for ( array_iter = &array_begin;
        array_iter < &array_end;
        array_iter += strlen(array_iter) + 1 ){
    printf ( "%3d:%s\n", strlen(array_iter), array_iter );
  }

  return 0;
}

其中 arraybegin, arrayend 为外部变量,他们会在 ld script 中定义。首先使用 ld –verbose 得到一份 ld 的默认脚本(去掉开 头和结尾的多余内容输出),存为 arrayiter.ld 文件。 找到 .data 断的定义,修改为类似如下的内容 :

#+name arrayiter.ld (partial)

  .data           :
  {
    array_begin = .;
    *(.extarray)
    array_end = .;

    *(.data .data.* .gnu.linkonce.d.*)
    KEEP (*(.gnu.linkonce.d.*personality*))
    SORT(CONSTRUCTORS)
  }

其中 "*(.extarray)"的意思是,将所有输入目标文件的 .extarray 段输出到这里,并且在周围分别设上两个变量 arraybegin, arrayend,他们就是在 main.c 中定义的外部变量。他们都被赋予了固定特殊变量".","."的意思是当前地址。

再来看 Makefile 如何写 :

#+name Makefile

OBJS=

array_iter : extarray.h array_iter.ld main.o $(OBJS)
        gcc $(CFLAGS) -Wl,-T,array_iter.ld main.o $(OBJS) -o $@

clean :
        rm -f *.o  array_iter

添加模块

在上面的 Makefile 中 OBJS 列表为空,我们现在就来添加几个模块 :

#+name extarray.h

typedef const char extarray_t[];

#define extarray extarray_t __attribute__((section(".extarray")))

#+name 1.c

#include "extarray.h"

extarray a1 = "1";
extarray a2 = "test 1";
extarray a3 = "";

#+name 2.c

#include "extarray.h"

extarray a4 = "HHIIOOIIYY";

修改 Makefile, 添加上:

OBJS=1.o 2.o~

然后执行命令 :

$ make
cc -pipe -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -g   -c -o main.o main.c
cc -pipe -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -g   -c -o 1.o 1.c
cc -pipe -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -g   -c -o 2.o 2.c
gcc -pipe -Wall -W -Wstrict-prototypes -Wmissing-prototypes -Wmissing-declarations -g -Wl,-T,array_iter.ld main.o 1.o 2.o -o array_iter

$ ./array_iter
array_begin : 0x80496d8 , array_end : 0x80496ed
  1:1
  6:test 1
  0:
 10:HHIIOOIIYY

无须修改其他文件,字符串已经被准确输出了。

再来看看内存模型 :

$ objdump -t array_iter| fgrep '.data' | sort -n
080496d8 g       .data  00000000              array_begin
080496d8 g     O .data  00000002              a1
080496d8 l    d  .data  00000000              .data
080496da g     O .data  00000007              a2
080496e1 g     O .data  00000001              a3
080496e2 g     O .data  0000000b              a4
080496ed g       .data  00000000              array_end
080496f0 g       .data  00000000              __data_start
080496f0  w      .data  00000000              data_start

注意到因为 arraybegin, arrayend 被赋予了一个常数,所以连接器没有为他们分配空间。

现实例子

在 PC x86 中,有多种通用的显示输出方式,常用的如 bios 提供的以字符为基础的输入输出例程,还有就是 vesa 2.0 显卡规范。在 linux x86 启动过程中,kernel 根据配置选择合适的视频输出设备。 实现中, kernel 就用到了 ldscript, 见 : linux-2.6/arch/x86/boot/setup.ld :

        .videocards     : {
                video_cards = .;
                *(.videocards)
                video_cards_end = .;
        }

linux-2.6/arch/x86/boot/video.h :
...
#define __videocard struct card_info __attribute__((section(".videocards")))
...
extern struct card_info video_cards[], video_cards_end[];
...
Tags: system programming c programming
30 Jun 2007

冲刺ELF极限——最小ELF程序

tinyso1.S:

; 最近研究ELF格式,hack了一把。
; 参考文档:
; 1。 ELF_format.pdf http://www.skyfree.org/linux/references/ELF_Format.pdf
; 2。A Whirlwind Tutorial on Creating Really Teensy ELF Executables for Linux
; http://www.muppetlabs.com/~breadbox/software/tiny/teensy.html
; 各文件:
;;; **************************************************
; tinyso1.S
;;; **************************************************
.global _start
_start:
    xor %eax, %eax
    inc %eax
    movw $filesize,%bx
    int  $0x80
.equ filesize, . - _start

;;; **************************************************
; tinyso2.S
;;; **************************************************
/* ELF Header */
ehdr:
    /* e_ident */
    .byte 0x7F,'E,'L,'F
    .byte 1 # EI_CLASS : ELFCLASS32
    .byte 1 # EI_DATA  : ELFDATA2LSB
    .byte 1 # EI_VERSION
    .rept 9
        .byte 0
    .endr
    .word 2        # e_type : ET_EXEC
    .word 3        # e_machine : EM_386
    .int  1        # e_version
    .int  _start   # e_entry
    .int  phdr_off # e_phoff
    .int  0        # e_shoff
    .int  0        # e_flags
    .word ehdrsize # e_ehsize
  .word phdrsize # e_phentsize
    .word 1        # e_phnum
    .word 0        # e_shentsize
    .word 0        # e_shnum
    .word 0        # e_shstrndx
.equ ehdrsize, . - ehdr
/* Program Headher */
.equ phdr_off, . - ehdr
phdr:
    .int  1          # p_type : PT_LOAD
    .int  0          # p_offset
    .int  ehdr       # p_vaddr
    .int  ehdr       # p_paddr (ignored)
    .int  filesize   # p_filesz
    .int  filesize   # p_memsz
    .int  5          # p_flags : r-x
    .int  0x1000     # p_align
.equ phdrsize, . - phdr
/* Code */
.global _start
_start:
    xor %eax,%eax
    inc %eax
    movw $filesize,%bx
    int  $0x80
.equ filesize, . - ehdr
;;; **************************************************
; tinyso3.S
;;; **************************************************

/* ELF Header 16 bytes, with embed CODE into preserved area */
ehdr:
    /* e_ident */
    .byte 0x7F,'E,'L,'F
    .byte 1 # EI_CLASS : ELFCLASS32
    .byte 1 # EI_DATA  : ELFDATA2LSB
    .byte 1 # EI_VERSION
    .global _start
/*
 * <HACK> 1: embed CODE into preserved area
 * exactly, 9 bytes!
 */
_start:
    xor %eax,%eax        # 2 bytes
    inc %eax             # 1 bytes
    movw $filesize,%bx   # 4 bytes
    int  $0x80           # 2 bytes
    .word 2        # e_type : ET_EXEC
    .word 3        # e_machine : EM_386
    .int  1        # e_version
    .int  _start   # e_entry
    .int  phdr_off # e_phoff
    .int  0        # e_shoff
    .int  0        # e_flags
    .word ehdrsize # e_ehsize
  .word phdrsize # e_phentsize
/* Program Headher */
.equ phdr_off, . - ehdr
phdr:
/*
 * <HACK> 2 : overlap two section
 * .word 1        # e_phnum
 * .word 0        # e_shentsize
 * .word 0        # e_shnum
 * .word 0        # e_shstrndx
 */
    .int  1          # p_type : PT_LOAD
    .int  0          # p_offset
.equ ehdrsize, . - ehdr
    .int  ehdr       # p_vaddr
    .int  ehdr       # p_paddr (ignored)
    .int  filesize   # p_filesz
    .int  filesize   # p_memsz
    .int  5          # p_flags : r-x
    .int  0x1000     # p_align
.equ phdrsize, . - phdr
.equ filesize, . - ehdr

Makefile:

all:tinyso1 tinyso2 tinyso3
tinyso1: tinyso1.S
    @echo ----------   $@  --------------
    gcc -nostdlib $< -o $@
    strip -s $@
    wc -c $@
    @echo ---------------------------------
tinyso2: tinyso2.S
    @echo -----------  $@  ---------------
    gcc -c $<
    ld tinyso2.o --oformat binary -o $@
    @echo ---------------------------------
tinyso3: tinyso3.S
    @echo -----------  $@  ----------------
    gcc -c $<
    ld tinyso3.o --oformat binary -o $@
    @echo ---------------------------------
clean:
    rm -f *.o tinyso1 tinyso2 tinyso3

效果:

hellwolf@cocteau#pts/9%J0S2:tinyso$wc -c tinyso?
264 tinyso1
 93 tinyso2
 76 tinyso3
433 total
hellwolf@cocteau#pts/9%J0S2:tinyso$for i in $(seq 1 3);do ./tinyso$i;echo $?;done
9
93
76
hellwolf@cocteau#pts/9%J0S2:tinyso$

文档2中还有更hack的手法,能压缩到42个字节, lol

Tags: system programming
22 Jul 2005

关于volatile

$gcc --version
gcc (GCC) 4.0.0 20050519 (Red Hat 4.0.0-8)
Copyright (C) 2005 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

#+name cat 1.c

int main()
{
  int j=0;
  int i=0;

  for (i = 0; i < 0xBACCA; i++)
     j += i * i;
}
$gcc -g -O0 1.c -o 1-O0
(gdb) disas main
Dump of assembler code for function main:
......
0x08048364 <main+28>:   movl   $0x0,0xfffffff8(%ebp)
0x0804836b <main+35>:   movl   $0x0,0xfffffffc(%ebp)
0x08048372 <main+42>:   movl   $0x0,0xfffffffc(%ebp)
0x08048379 <main+49>:   jmp    0x804838e <main+70>
0x0804837b <main+51>:   mov    0xfffffffc(%ebp),%eax
0x0804837e <main+54>:   mov    %eax,%edx
0x08048380 <main+56>:   imul   0xfffffffc(%ebp),%edx
0x08048384 <main+60>:   lea    0xfffffff8(%ebp),%eax
0x08048387 <main+63>:   add    %edx,(%eax)
0x08048389 <main+65>:   lea    0xfffffffc(%ebp),%eax
0x0804838c <main+68>:   incl   (%eax)
0x0804838e <main+70>:   cmpl   $0xbacc9,0xfffffffc(%ebp)
0x08048395 <main+77>:   jle    0x804837b <main+51>
0x08048397 <main+79>:   leave
0x08048398 <main+80>:   ret

$gcc -g -O1 1.c -o 1-O1
(gdb) disas main
Dump of assembler code for function main:
0x08048348 <main+0>:    push   %ebp
0x08048349 <main+1>:    mov    %esp,%ebp
0x0804834b <main+3>:    sub    $0x8,%esp
0x0804834e <main+6>:    and    $0xfffffff0,%esp
0x08048351 <main+9>:    sub    $0x10,%esp
0x08048354 <main+12>:   mov    $0xbacca,%eax
0x08048359 <main+17>:   dec    %eax
0x0804835a <main+18>:   jne    0x8048359 <main+17>
0x0804835c <main+20>:   leave
0x0804835d <main+21>:   ret
End of assembler dump.

#+name 2.c

int main()
{
  volatile int j=0;
  int i=0;

  for (i = 0; i < 0xBACCA; i++)
     j += i * i;
}
$gcc -g -O0 2.c -o 2-O0

(gdb) disas main
Dump of assembler code for function main:
......
0x08048364 <main+28>:   movl   $0x0,0xfffffff8(%ebp)
0x0804836b <main+35>:   movl   $0x0,0xfffffffc(%ebp)
0x08048372 <main+42>:   movl   $0x0,0xfffffffc(%ebp)
0x08048379 <main+49>:   jmp    0x8048392 <main+74>
0x0804837b <main+51>:   mov    0xfffffffc(%ebp),%eax
0x0804837e <main+54>:   mov    %eax,%edx
0x08048380 <main+56>:   imul   0xfffffffc(%ebp),%edx
0x08048384 <main+60>:   mov    0xfffffff8(%ebp),%eax
0x08048387 <main+63>:   lea    (%edx,%eax,1),%eax
0x0804838a <main+66>:   mov    %eax,0xfffffff8(%ebp)
0x0804838d <main+69>:   lea    0xfffffffc(%ebp),%eax
0x08048390 <main+72>:   incl   (%eax)
0x08048392 <main+74>:   cmpl   $0xbacc9,0xfffffffc(%ebp)
0x08048399 <main+81>:   jle    0x804837b <main+51>
0x0804839b <main+83>:   leave
0x0804839c <main+84>:   ret
End of assembler dump.

$gcc -g -O1 2.c -o 2-O1

(gdb) disas main
Dump of assembler code for function main:
0x08048348 <main+0>:    push   %ebp
0x08048349 <main+1>:    mov    %esp,%ebp
0x0804834b <main+3>:    sub    $0x18,%esp
0x0804834e <main+6>:    and    $0xfffffff0,%esp
0x08048351 <main+9>:    sub    $0x10,%esp
0x08048354 <main+12>:   movl   $0x0,0xfffffffc(%ebp)
0x0804835b <main+19>:   mov    $0x0,%ecx
0x08048360 <main+24>:   mov    0xfffffffc(%ebp),%edx
0x08048363 <main+27>:   mov    %ecx,%eax
0x08048365 <main+29>:   imul   %ecx,%eax
0x08048368 <main+32>:   add    %edx,%eax
0x0804836a <main+34>:   mov    %eax,0xfffffffc(%ebp)
0x0804836d <main+37>:   inc    %ecx
0x0804836e <main+38>:   cmp    $0xbacca,%ecx
0x08048374 <main+44>:   jne    0x8048360 <main+24>
0x08048376 <main+46>:   leave
0x08048377 <main+47>:   ret

先分析-O0的情况:

0x08048364 <main+28>: movl $0x0,0xfffffff8(%ebp)           j=0;
0x0804836b <main+35>: movl $0x0,0xfffffffc(%ebp)           i=0;
0x08048372 <main+42>: movl $0x0,0xfffffffc(%ebp)           for(i=0;
0x08048379 <main+49>: jmp 0x804838e <main+70>              ->
0x0804837b <main+51>: mov 0xfffffffc(%ebp),%eax
0x0804837e <main+54>: mov %eax,%edx
0x08048380 <main+56>: imul 0xfffffffc(%ebp),%edx           i*i->EDX

0x08048384 <main+60>: lea 0xfffffff8(%ebp),%eax            EAX->j;
0x08048387 <main+63>: add %edx,(%eax)                      j+=EDX

0x08048389 <main+65>: lea 0xfffffffc(%ebp),%eax            eax->i;
0x0804838c <main+68>: incl (%eax)                          ++i;
0x0804838e <main+70>: cmpl $0xbacc9,0xfffffffc(%ebp)
0x08048395 <main+77>: jle 0x804837b <main+51>               ?i<0xbacca
0x08048397 <main+79>: leave
0x08048398 <main+80>: ret
0x08048364 <main+28>: movl $0x0,0xfffffff8(%ebp)            j=0;(volatile)
0x0804836b <main+35>: movl $0x0,0xfffffffc(%ebp)            i=0;
0x08048372 <main+42>: movl $0x0,0xfffffffc(%ebp)            for(i=0;
0x08048379 <main+49>: jmp 0x8048392 <main+74>               ->
0x0804837b <main+51>: mov 0xfffffffc(%ebp),%eax
0x0804837e <main+54>: mov %eax,%edx                         i*i->EDX,
0x08048380 <main+56>: imul 0xfffffffc(%ebp),%edx            到这里和上面都是一样的

0x08048384 <main+60>: mov 0xfffffff8(%ebp),%eax             EAX=j;
0x08048387 <main+63>: lea (%edx,%eax,1),%eax
                      这里有点花哨,就是%edx+%eax*1,j+=i*i的意思
0x0804838a <main+66>: mov %eax,0xfffffff8(%ebp)

0x0804838d <main+69>: lea 0xfffffffc(%ebp),%eax
0x08048390 <main+72>: incl (%eax)
0x08048392 <main+74>: cmpl $0xbacc9,0xfffffffc(%ebp)
0x08048399 <main+81>: jle 0x804837b <main+51>
0x0804839b <main+83>: leave
0x0804839c <main+84>: ret

再看这里 http://www.linuxdevices.com/articles/AT5980346182.html 关于volatile的介绍:

The reason to use volatile is to ensure that the compiler generates code to re-load a data item each time it is referenced in your program.

就不难理解上面的差异了,volatile的j每次都是存入到寄存器中而不是由寄存器指向其地址,即所谓的 "re-load a data item each time it's refenced."

Tags: system programming c programming
Other posts
Creative Commons License
A First-Principles-Driven Life by Miao, ZhiCheng is licensed under a Creative Commons Attribution 3.0 License.